use loco_rs::prelude::*;
use sea_orm::DeleteResult;
use serde::{Deserialize, Serialize};

pub use super::_entities::password_reset_tokens::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub email: String,
    pub token: String,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(email(message = "Email must be a valid email address."))]
    pub email: String,
    #[validate(length(min = 1, message = "Token must not be empty."))]
    pub token: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            email: self.email.as_ref().to_owned(),
            token: self.token.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::password_reset_tokens::ActiveModel {
    async fn before_save<C>(self, _db: &C, _insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        Ok(self)
    }
}

impl Model {
    /// finds a password reset token by the provided email
    ///
    /// # Errors
    ///
    /// When could not find password reset token or DB query error
    pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult<Self> {
        let token = password_reset_tokens::Entity::find()
            .filter(
                model::query::condition()
                    .eq(password_reset_tokens::Column::Email, email)
                    .build(),
            )
            .one(db)
            .await?;
        token.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// Asynchronously creates a password reset token and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the password reset token into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let token = password_reset_tokens::ActiveModel {
            email: ActiveValue::set(params.email.to_string()),
            token: ActiveValue::set(params.token.to_string()),
            ..Default::default()
        }
        .insert(db)
        .await?;

        Ok(token)
    }

    /// Deletes the password reset token
    ///
    /// # Errors
    ///
    /// When could not delete the password reset token from the DB
    pub async fn delete(self, db: &DatabaseConnection) -> ModelResult<DeleteResult> {
        let model: password_reset_tokens::ActiveModel = self.into();
        Ok(model.delete(db).await?)
    }
}
